package net.hands.ado.db;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import net.hands.ado.ADOException;
import net.hands.ado.db.jdbc.IJdbcProvider;
import net.hands.ado.db.jdbc.IQueryExtractor;
import net.hands.common.ado.query.AbstractDataQuery;
import net.hands.common.coll.LRUMap;

public class DBDataQuery extends AbstractDataQuery<Map<String, Object>>
		implements IDBDataQuery<Map<String, Object>> {

	/* jdbc提供者 */
	private IJdbcProvider jdbcProvider;

	private SQLValue sqlVal;

	/* 分页取值的大小, 当等于0时, 要手动close */
	private int fetchSize = -1;

	/* 分页取得的数据缓存 */
	private Map<Integer, Map<String, Object>> dataCache;

	public int getFetchSize() {
		if (fetchSize < 0 || fetchSize >= Integer.MAX_VALUE) {
			setFetchSize(100);
		}
		return fetchSize;
	}

	private Map<Integer, Map<String, Object>> getDataCache() {
		if (dataCache == null) {
			dataCache = Collections
					.synchronizedMap(new LRUMap<Integer, Map<String, Object>>(
							getFetchSize() * 5));
		}
		return dataCache;
	}

	@Override
	public void reset() {
		super.reset();
		dataCache = null;
	}

	@Override
	public DBDataQuery setFetchSize(int fetchSize) {
		if (this.fetchSize != fetchSize) {
			this.fetchSize = fetchSize;
			dataCache = null;
		}
		return this;
	}

	public DBDataQuery(final IJdbcProvider jdbcProvider, final SQLValue sqlVal) {
		this.jdbcProvider = jdbcProvider;
		this.sqlVal = sqlVal;
	}

	public DBDataQuery(final IJdbcProvider jdbcProvider, final String sql,
			final Object[] values) {
		this(jdbcProvider, new SQLValue(sql, values));
	}

	@Override
	public DataSource getDataSource() {
		return jdbcProvider.getDataSource();
	}

	@Override
	public int getCount() {
		if (count < 0) {
			final SQLValue countSql = new SQLValue(jdbcProvider
					.getJdbcDialect().toCountSQL(sqlVal.getSql()),
					sqlVal.getValues());
			count = jdbcProvider.queryObject(countSql,
					new IQueryExtractor<Integer>() {
						@Override
						public Integer extractData(final ResultSet rs)
								throws SQLException, ADOException {
							return rs.next() ? rs.getInt(1) : 0;
						}
					});
		}
		return count;
	}

	private Connection _conn;
	private PreparedStatement _ps;
	private ResultSet _rs;

	public Map<String, Object> mapRow(final ResultSet rs) throws SQLException {
		// E bean = null;
		// bean =
		// return null;
		ResultSetMetaData rsmd = rs.getMetaData();
		int count = rsmd.getColumnCount();
		Map<String, Object> map = new HashMap<>();
		for (int i = 1; i <= count; i++) {
			map.put(rsmd.getColumnName(i), jdbcProvider.getJdbcDialect()
					.getResultSetValue(rs, i));// 把列名存入向量heads中
		}

		return map;
	}

	@Override
	public void close() {
		try {
			if (_conn != null) {
				_conn.close();
			}
			if (_ps != null) {
				_ps.close();
			}
			if (_rs != null) {
				_rs.close();
			}
		} catch (final SQLException e) {
			log.warn(e);
		}
	}

	@Override
	public Map<String, Object> next() {
		Map<String, Object> map = null;
		i++;
		final int fetchSize = getFetchSize();
		if (fetchSize <= 0) {
			try {
				if (i == 0 && _rs == null) {
					_conn = getDataSource().getConnection();
					_ps = jdbcProvider.getStatementCreator().prepareStatement(
							_conn, sqlVal, getResultSetType(),
							getResultSetConcurrency());
					_rs = _ps.executeQuery();
				}
				if (_rs != null) {
					if (_rs.next()) {
						map = mapRow(_rs);
					} else {
						close();
					}
				}
			} catch (final Exception e) {
				throw ADOException.of(e);
			}
		} else if (i < getCount() && i >= 0) {
			final Map<Integer, Map<String, Object>> dataCache = getDataCache();
			map = dataCache.get(i);
			if (map == null) {
				final String sql = sqlVal.getSql();
				final String lsql = jdbcProvider.getJdbcDialect().toLimitSQL(
						sql, i, fetchSize);
				try {
					_conn = getDataSource().getConnection();
					_ps = jdbcProvider.getStatementCreator().prepareStatement(
							_conn, new SQLValue(lsql, sqlVal.getValues()),
							getResultSetType(), getResultSetConcurrency());
					_rs = _ps.executeQuery();
					int j = -1;
					while (_rs.next()) {
						final int k = i + ++j;
						if (dataCache.containsKey(k)) {
							break;
						}
						final Map<String, Object> row = mapRow(_rs);

						dataCache.put(k, row);
						// 在oracle测试中
						if (j == fetchSize - 1) {
							break;
						}
					}
				} catch (final Exception e) {
					throw ADOException.of(e);
				}

			}

		}

		return map;
	}

	private int resultSetType, resultSetConcurrency;

	int getResultSetType() {
		if (resultSetType == 0) {
			resultSetType = jdbcProvider.getJdbcDialect().getResultSetType();
		}
		return resultSetType;
	}

	int getResultSetConcurrency() {
		if (resultSetConcurrency == 0) {
			resultSetConcurrency = ResultSet.CONCUR_READ_ONLY;
		}
		return resultSetConcurrency;
	}

	@Override
	public SQLValue getSqlValue() {
		return null;
	}

	@Override
	public boolean hasNext() {
		// TODO Auto-generated method stub
		return false;
	}

}
